﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		PageFlipEffect.cs
//
//	Author:		MJP
//
//	Date:		05/07/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Drawing.Design;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CPXMenu
{
    /// <summary>
    /// Used to control the visual effects when a page flip occurs
    /// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]   
    public abstract class PageFlipEffect
    {
        protected float flipTime = 0;

		/// <summary>
		/// Gets or sets the amound of time it will take for the transition to occur
		/// </summary>
        [Browsable(true)]
        [Description("The time it will take for the transition to occur")]
        public float FlipTime
        {
            get { return flipTime; }
            set { flipTime = value; }
        }

		/// <summary>
		/// Gets a description of the PageFlipEffect
		/// </summary>
        [Browsable(false)]
        public string Description
        {
            get
            {
                DescriptionAttribute attrib = GetCustomAttribute<DescriptionAttribute>(this.GetType());
                if (attrib != null)
                    return attrib.Description;
                else
                    return string.Empty;
            }
        }

		/// <summary>
		/// Converts the instance to a string representation
		/// </summary>
		/// <returns>A string representation of the object</returns>
        public override string ToString()
        {
            DisplayNameAttribute attrib = GetCustomAttribute<DisplayNameAttribute>(this.GetType());
            if (attrib != null)
                return attrib.DisplayName;
            else
                return this.GetType().Name;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal abstract void Render(float dt, 
                                        float currentFlipTime, 
                                        GraphicsDevice graphicsDevice, 
                                        SpriteBatch spriteBatch, 
                                        RoundLineManager lineManager,
                                        Page flipFromPage, 
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor);

        /// <summary>
        /// Returns true of the type is a PageFlipEffect derivative
        /// </summary>
        /// <param name="type">The type to check</param>
        /// <returns>True if the type is a PageFlipEffect</returns>
        public static bool IsPageFlipEffectType(Type type)
        {
            if (type == typeof(PageFlipEffect))
                return true;
            else
            {
                if (type.BaseType != null)
                    return IsPageFlipEffectType(type.BaseType);
                else
                    return false;
            }
        }

        /// <summary>
        /// Gets an array of all PageFlipEffect derivatives in the assembly
        /// </summary>
        /// <returns>The array of PageFlipEffect types</returns>
        public static Type[] GetPageFlipEffectTypes()
        {
            List<Type> flipEffectTypes = new List<Type>();

            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "CPXMenu";

            Assembly assembly = Assembly.Load(assemblyName);
            List<Type> objectTypes = new List<Type>();
            foreach (Type type in assembly.GetTypes())
                if (IsPageFlipEffectType(type))
                    flipEffectTypes.Add(type);

            return flipEffectTypes.ToArray();
        }

        /// <summary>
        /// Gets a custom Attribute assigned to this PageFlipEffect's type
        /// </summary>
        /// <typeparam name="TAttribute">The Attribute to retrieve</typeparam>
        /// <returns>The specified attribute, if applied to this type.  null otherwise.</returns>
        private static TAttribute GetCustomAttribute<TAttribute>(Type flipEffectType) where TAttribute : Attribute
        {
            Debug.Assert(IsPageFlipEffectType(flipEffectType));

            object[] attributes = flipEffectType.GetCustomAttributes(false);
            foreach (object attribute in attributes)
                if (attribute is TAttribute)
                    return attribute as TAttribute;
            return null;
        }        
    }

    /// <summary>
    /// A dead-simple instant page flip
    /// </summary>
    [Description("Causes the project instantly flip from one page to another")]
    [DisplayName("Instant")]
    public class InstantPageFlipEffect : PageFlipEffect
    {
		/// <summary>
		/// Gets or sets the transition time.  Setting has no effect, always returns 0.
		/// </summary>
        [Browsable(false)]
        public new float FlipTime
        {
            get { return 0; }
            set { }
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt, 
                                        float currentFlipTime, 
                                        GraphicsDevice graphicsDevice, 
                                        SpriteBatch spriteBatch, 
                                        RoundLineManager lineManager,
                                        Page flipFromPage, 
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {            
        }
    }

    /// <summary>
    /// Fades from one page to the other
    /// </summary>
    [Description("Smoothly fades from one page to the other")]
    [DisplayName("Crossfade")]
    public class CrossfadePageFlipEffect : PageFlipEffect
    {
		/// <summary>
		/// Creates a new instance of CrossfadePageFlipEffect
		/// </summary>
        public CrossfadePageFlipEffect()
            : base()
        {
            flipTime = 1.0f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime, 
                                        GraphicsDevice graphicsDevice, 
                                        SpriteBatch spriteBatch, 
                                        RoundLineManager lineManager,
                                        Page flipFromPage, 
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.RenderState.AlphaBlendEnable = false;

            graphicsDevice.SetRenderTarget(0, prevRenderTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			
            float alpha = currentFlipTime / flipTime;

            postProcessor.Blend(flipFromTarget, flipToTarget, prevRenderTarget, alpha);         
        }
    }

    /// <summary>
    /// Fades from one page to the other
    /// </summary>
    [Description("Blurs out the first page, then brings the next page into focus")]
    [DisplayName("BlurOutBlurIn")]
    public class BlurOutBlurInPageFlipEffect : PageFlipEffect
    {
		/// <summary>
		/// Creates a new instance of BlurOutBlurInPageFlipEffect
		/// </summary>
        public BlurOutBlurInPageFlipEffect()
            : base()
        {
            flipTime = 1.0f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime,
                                        GraphicsDevice graphicsDevice,
                                        SpriteBatch spriteBatch,
                                        RoundLineManager lineManager,
                                        Page flipFromPage,
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipFromBlurred = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToBlurred = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            float alpha = currentFlipTime / flipTime;

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.RenderState.AlphaBlendEnable = false;

            postProcessor.Blur(flipFromTarget, flipFromBlurred, alpha * 2);
            postProcessor.Blur(flipToTarget, flipToBlurred, 2 - (alpha * 2));
            postProcessor.Blend(flipFromBlurred, flipToBlurred, prevRenderTarget, alpha * 2 - 1);
        }
    }

    /// <summary>
    /// Fades out the first page, then fades in the next
    /// </summary>
    [Description("Fades out the first page, then fades in the next")]
    [DisplayName("FadeOutFadeIn")]
    public class FadeOutFadeInPageFlipEffect : PageFlipEffect
    {
		/// <summary>
		/// Creates a new instance of FadeOutFadeInPageFlipEffect
		/// </summary>
        public FadeOutFadeInPageFlipEffect()
            : base()
        {
            flipTime = 1.0f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime,
                                        GraphicsDevice graphicsDevice,
                                        SpriteBatch spriteBatch,
                                        RoundLineManager lineManager,
                                        Page flipFromPage,
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);			
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, prevRenderTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.Black, 1, 0);

            float alpha1 = 0;
            float alpha2 = 0;

            if (currentFlipTime < flipTime / 2.0f)
            {
                alpha1 = 1 - MathHelper.Clamp((currentFlipTime / flipTime) * 3.0f, 0, 1);
                alpha2 = 0;
            }
            else
            {
                alpha2 = MathHelper.Clamp((currentFlipTime / flipTime) * 3.0f - 1.333f, 0, 1);
                alpha1 = 0;
            }

			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);			
            spriteBatch.Draw(flipFromTarget.GetTexture(), flipFromPage.BoundingRect, new Color(Color.White, alpha1));
            spriteBatch.Draw(flipToTarget.GetTexture(), flipFromPage.BoundingRect, new Color(Color.White, alpha2));
            spriteBatch.End();
        }
    }

    /// <summary>
    /// Slides in the new page over the old page
    /// </summary>
    [Description("Causes the new page to slide in over the old page")]
    [DisplayName("Slide")]
    public class SlidePageFlipEffect : PageFlipEffect
    {
        private SlideDirection slideDirection = SlideDirection.Left;

		/// <summary>
		/// Gets or sets the direction in which the new page will slide in over the old page
		/// </summary>
        [Browsable(true)]
        [Description("The direction in which the new page will slide in over the old page")]
        public SlideDirection Direction
        {
            get { return slideDirection; }
            set { slideDirection = value; }
        }

		/// <summary>
		/// Creates a new instance of SlidePageFlipEffect
		/// </summary>
        public SlidePageFlipEffect()
            : base()
        {
            flipTime = 0.5f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime,
                                        GraphicsDevice graphicsDevice,
                                        SpriteBatch spriteBatch,
                                        RoundLineManager lineManager,
                                        Page flipFromPage,
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, prevRenderTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);

            Rectangle drawRect = flipToPage.BoundingRect;

			// It's electric!  (boogie-oogie-oogie-oogie)
            if (slideDirection == SlideDirection.Left)
                drawRect.X = (int)(flipToPage.Dimensions.X - ((currentFlipTime / flipTime) * flipToPage.Dimensions.X));
            else if (slideDirection == SlideDirection.UpLeft)
            {
                drawRect.X = (int)(flipToPage.Dimensions.X - ((currentFlipTime / flipTime) * flipToPage.Dimensions.X));
                drawRect.Y = (int)(flipToPage.Dimensions.Y - ((currentFlipTime / flipTime) * flipToPage.Dimensions.Y));
            }
            else if (slideDirection == SlideDirection.DownLeft)
            {
                drawRect.X = (int)(flipToPage.Dimensions.X - ((currentFlipTime / flipTime) * flipToPage.Dimensions.X));
                drawRect.Y = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.Y) - flipToPage.Dimensions.Y;
            }
            else if (slideDirection == SlideDirection.Right)
                drawRect.X = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.X) - flipToPage.Dimensions.X;
            else if (slideDirection == SlideDirection.UpRight)
            {
                drawRect.X = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.X - flipToPage.Dimensions.X);
                drawRect.Y = (int)(flipToPage.Dimensions.Y - ((currentFlipTime / flipTime) * flipToPage.Dimensions.Y));
            }
            else if (slideDirection == SlideDirection.DownRight)
            {
                drawRect.X = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.X) - flipToPage.Dimensions.X;
                drawRect.Y = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.Y) - flipToPage.Dimensions.Y;
            }
            else if (slideDirection == SlideDirection.Up)
                drawRect.Y = (int)(flipToPage.Dimensions.Y - ((currentFlipTime / flipTime) * flipToPage.Dimensions.Y));
            else if (slideDirection == SlideDirection.Down)
                drawRect.Y = (int)((currentFlipTime / flipTime) * flipToPage.Dimensions.Y) - flipToPage.Dimensions.Y;

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            spriteBatch.Draw(flipFromTarget.GetTexture(), flipFromPage.BoundingRect, Color.White);
            spriteBatch.Draw(flipToTarget.GetTexture(), drawRect, Color.White);
            spriteBatch.End();
            
        }

		/// <summary>
		/// Used to specify which direction the new page should slide in
		/// over the old page.
		/// </summary>
        public enum SlideDirection
        {
            Left = 0,
            UpLeft = 1,
            Up = 2,
            UpRight = 3,
            Right = 4,
            DownRight = 5,
            Down = 6,
            DownLeft = 7
        }
    }

    /// <summary>
    /// Rotates the old page out, and the new page in
    /// </summary>
    [Description("Rotates the old page out, and the new page in")]
    [DisplayName("RotateOutAndIn")]
    public class RotateOutAndInPageFlipEffect : PageFlipEffect
    {
		/// <summary>
		/// Creates a new instance of RotateOutAndInPageFlipEffect
		/// </summary>
        public RotateOutAndInPageFlipEffect()
            : base()
        {
            flipTime = 1.0f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime,
                                        GraphicsDevice graphicsDevice,
                                        SpriteBatch spriteBatch,
                                        RoundLineManager lineManager,
                                        Page flipFromPage,
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, prevRenderTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);

            float alpha = currentFlipTime / flipTime;
            float rotation = alpha * 2 * MathHelper.Pi;

            Rectangle drawRect = flipToPage.BoundingRect;

            float alpha1 = 1;
            float alpha2 = 0;
            if (currentFlipTime >= flipTime / 2)
            {
                alpha1 = 0;
                alpha2 = 1;
            }

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            spriteBatch.Draw(flipFromTarget.GetTexture(), drawRect, null, new Color(Color.White, alpha1), rotation, Vector2.Zero, SpriteEffects.None, 0);
            spriteBatch.Draw(flipToTarget.GetTexture(), drawRect, null, new Color(Color.White, alpha2), rotation, Vector2.Zero, SpriteEffects.None, 0);
            spriteBatch.End();

        }
    }
    

    /// <summary>
    /// Fades from one page to the other
    /// </summary>
    [Description("Smoothly fades from one page to the other")]
    [DisplayName("CrossfadeAndRotate")]
    public class CrossfadeAndRotatePageFlipEffect : PageFlipEffect
    {
        private int numRotations = 1;

        /// <summary>
        /// Gets or sets the number of times the page will rotate while flipping
        /// </summary>
        [Browsable(true)]
        [Description("The number of times the page will rotate while flipping")]
        public int NumRotations
        {
            get { return numRotations; }
            set { numRotations = value; }
        }                

		/// <summary>
		/// Creates a new instance of CrossfadeAndRotatePageFlipEffect
		/// </summary>
        public CrossfadeAndRotatePageFlipEffect()
            : base()
        {
            flipTime = 1.0f;
        }

		/// <summary>
		/// Renders the PageFlipEffect
		/// </summary>
		/// <param name="dt">The time delta, in seconds</param>
		/// <param name="currentFlipTime">The amount of time that has elapsed since the PageFlip began</param>
		/// <param name="graphicsDevice">The GraphicsDevice</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="flipFromPage">The page being flipped from</param>
		/// <param name="flipToPage">The page being flipped to</param>
		/// <param name="postProcessor">The shared PageFlipPostProcessor instance</param>
        internal override void Render(float dt,
                                        float currentFlipTime,
                                        GraphicsDevice graphicsDevice,
                                        SpriteBatch spriteBatch,
                                        RoundLineManager lineManager,
                                        Page flipFromPage,
                                        Page flipToPage,
                                        PageFlipPostProcessor postProcessor)
        {
            RenderTarget2D prevRenderTarget = graphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            RenderTarget2D flipFromTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);
            RenderTarget2D flipToTarget = postProcessor.GetRenderTarget(flipFromPage.Dimensions);

            graphicsDevice.SetRenderTarget(0, flipFromTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipFromPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, flipToTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
            flipToPage.Render(dt, spriteBatch, lineManager, null, null);

            graphicsDevice.SetRenderTarget(0, prevRenderTarget);
            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);
			flipFromPage.ParentProject.RenderThis(dt, spriteBatch, lineManager, false, false);

            float alpha = currentFlipTime / flipTime;
            float rotation = alpha * 2 * MathHelper.Pi * numRotations;

            Vector2 origin = new Vector2(flipToPage.Dimensions.X / 2.0f, flipToPage.Dimensions.Y / 2.0f);
            
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);            
            spriteBatch.Draw(flipFromTarget.GetTexture(), origin, null, new Color(Color.White, 1 - alpha), rotation, origin, 1.0f, SpriteEffects.None, 0);
            spriteBatch.End();

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); 
            spriteBatch.Draw(flipToTarget.GetTexture(), origin, null, new Color(Color.White, alpha), rotation, origin, 1.0f, SpriteEffects.None, 0);
            spriteBatch.End();
        }
    }
       
}